﻿namespace Stickam_Client
{
    public __gc class SocketConnector
    {
        // Nested Types
        public: __delegate void __gc* ClosedEventHandler();


        public: __delegate void __gc* ConnectedEventHandler();


        public: __delegate void __gc* ConnectFailEventHandler();


        public: __delegate void __gc* DataArrivalEventHandler(String __gc* Data);


        // Events
        public: __event ClosedEventHandler __gc* Closed;
        public: __event ConnectedEventHandler __gc* Connected;
        public: __event ConnectFailEventHandler __gc* ConnectFail;
        public: __event DataArrivalEventHandler __gc* DataArrival;

        // Methods
        public: void __gc* Close()
        {
            this->CloseSocket(false);
        }

        private: void __gc* CloseSocket(Boolean __gc* DoEvent)
        {
            if (this->SockRunning)
            {
                this->SockRunning = false;
                if (this->SockConnected)
                {
                    this->SockConnected = false;
                    this->Sock->Shutdown(SocketShutdown::Both);
                }
                this->Sock->Close();
            }
            if (DoEvent)
            {
                ClosedEventHandler __gc* closedEvent = this->ClosedEvent;
                if (closedEvent != 0)
                {
                    closedEvent->Invoke();
                }
            }
        }

        public: void __gc* Connect(String __gc* IP, String __gc* SocksIP, Int32 __gc* SocksVer, Boolean __gc* SocksEnabled)
        {
            if (!this->SockRunning)
            {
                String __gc* str;
                if (SocksEnabled)
                {
                    str = SocksIP;
                    this->ServerIP = IP;
                    this->SocksAuthenticated = false;
                    this->SocksVersion = SocksVer;
                }
                else
                {
                    str = IP;
                    this->SocksAuthenticated = true;
                }
                String __gc* strArray2 __gc [] = Strings::Split(str, S":", -1, CompareMethod::Binary);
                String __gc* strArray __gc [] = Strings::Split(strArray2[0], S".", -1, CompareMethod::Binary);
                Byte __gc* address __gc [] = __gc new Byte __gc*[4] {
                    *static_cast<__box Byte*>(Conversions::ToInteger(strArray[0])), *static_cast<__box Byte*>(Conversions::ToInteger(strArray[1])), *static_cast<__box Byte*>(Conversions::ToInteger(strArray[2])), *static_cast<__box Byte*>(Conversions::ToInteger(strArray[3]))};
                IPEndPoint __gc* remoteEP = __gc new IPEndPoint(__gc new IPAddress(address), Conversions::ToInteger(strArray2[1]));
                this->Sock = __gc new Socket(AddressFamily::InterNetwork, SocketType::Stream, ProtocolType::Tcp);
                this->SockRunning = true;
                this->Sock->BeginConnect(remoteEP, new AsyncCallback __gc*(this.ConnectCallback), 0);
            }
        }

        private: void __gc* ConnectCallback(IAsyncResult __gc* ar)
        {
            if (this->SockRunning)
            {
                try
                {
                    this->Sock->EndConnect(ar);
                    this->SockConnected = true;
                    if (this->SocksAuthenticated)
                    {
                        ConnectedEventHandler __gc* connectedEvent = this->ConnectedEvent;
                        if (connectedEvent != 0)
                        {
                            connectedEvent->Invoke();
                        }
                    }
                    else
                    {
                        this->SocksConnect();
                    }
                    this->ReceiveData();
                }
                catch (Exception __gc* exception1)
                {
                    ProjectData::SetProjectError(exception1);
                    Exception __gc* exception = exception1;
                    this->ConnectionFailed();
                    ProjectData::ClearProjectError();
                }
            }
        }

        private: void __gc* ConnectionFailed()
        {
            if (this->SockRunning)
            {
                this->SockRunning = false;
                if (this->SockConnected)
                {
                    this->SockConnected = false;
                    this->Sock->Shutdown(SocketShutdown::Both);
                }
                this->Sock->Close();
            }
            ConnectFailEventHandler __gc* connectFailEvent = this->ConnectFailEvent;
            if (connectFailEvent != 0)
            {
                connectFailEvent->Invoke();
            }
        }

        private: String __gc* GetIPBytes(String __gc* IP, Int32 __gc* SocksOrder)
        {
            String __gc* strArray2 __gc [] = Strings::Split(IP, S":", -1, CompareMethod::Binary);
            String __gc* strArray __gc [] = Strings::Split(strArray2[0], S".", -1, CompareMethod::Binary);
            Int64 __gc* num2 = Conversions::ToLong(strArray2[1]);
            Double __gc* a = Conversion::Fix(*static_cast<__box Double*>((*static_cast<__box Double*>(num2) / 256)));
            num2 = *static_cast<__box Int64*>(Math::Round(*static_cast<__box Double*>((num2 - (256 * a)))));
            String __gc* str2 = String::Concat(Conversions::ToString(Strings::Chr(Conversions::ToInteger(strArray[0]))), Conversions::ToString(Strings::Chr(Conversions::ToInteger(strArray[1]))), Conversions::ToString(Strings::Chr(Conversions::ToInteger(strArray[2]))), Conversions::ToString(Strings::Chr(Conversions::ToInteger(strArray[3]))));
            String __gc* str3 = String::Concat(Conversions::ToString(Strings::Chr(*static_cast<__box Int32*>(Math::Round(a)))), Conversions::ToString(Strings::Chr(*static_cast<__box Int32*>(num2))));
            if (SocksOrder == 4)
            {
                return String::Concat(str3, str2);
            }
            return String::Concat(str2, str3);
        }

        private: void __gc* HandleData(String __gc* Data)
        {
            if (this->SocksAuthenticated)
            {
                DataArrivalEventHandler __gc* dataArrivalEvent = this->DataArrivalEvent;
                if (dataArrivalEvent != 0)
                {
                    dataArrivalEvent->Invoke(Data);
                }
            }
            else
            {
                ConnectedEventHandler __gc* connectedEvent;
                if (this->SocksVersion == 4)
                {
                    if (Strings::Asc(Strings::Mid(Data, 2, 1)) == 90)
                    {
                        this->SocksAuthenticated = true;
                        connectedEvent = this->ConnectedEvent;
                        if (connectedEvent != 0)
                        {
                            connectedEvent->Invoke();
                        }
                    }
                    else
                    {
                        this->ConnectionFailed();
                    }
                }
                else if (Data->Length == 2)
                {
                    if (Strings::Mid(Data, 1, 2) == S"\0")
                    {
                        this->SendData(String::Concat(S"\0", this->GetIPBytes(this->ServerIP, 5)));
                    }
                    else
                    {
                        this->ConnectionFailed();
                    }
                }
                else if (Strings::Mid(Data, 1, 3) == S"\0\0")
                {
                    this->SocksAuthenticated = true;
                    connectedEvent = this->ConnectedEvent;
                    if (connectedEvent != 0)
                    {
                        connectedEvent->Invoke();
                    }
                }
                else
                {
                    this->ConnectionFailed();
                }
            }
        }

        private: void __gc* ReceiveCallback(IAsyncResult __gc* ar)
        {
            if (this->SockRunning)
            {
                try
                {
                    Int32 __gc* count = this->Sock->EndReceive(ar);
                    if (count == 0)
                    {
                        this->CloseSocket(true);
                    }
                    else
                    {
                        this->HandleData(Globals::Encoder->GetString(this->Buffer, 0, count));
                        this->ReceiveData();
                    }
                }
                catch (Exception __gc* exception1)
                {
                    ProjectData::SetProjectError(exception1);
                    Exception __gc* exception = exception1;
                    this->CloseSocket(true);
                    ProjectData::ClearProjectError();
                }
            }
        }

        private: void __gc* ReceiveData()
        {
            this->Sock->BeginReceive(this->Buffer, 0, this->Buffer->Length, SocketFlags::None, new AsyncCallback __gc*(this.ReceiveCallback), 0);
        }

        public: void __gc* SendData(String __gc* Data)
        {
            if (this->SockRunning & this->SockConnected)
            {
                this->Sock->Send(Globals::Encoder->GetBytes(Data));
            }
        }

        private: void __gc* SocksConnect()
        {
            if (this->SocksVersion == 4)
            {
                this->SendData(String::Concat(S"", this->GetIPBytes(this->ServerIP, 4), S"Owner\0"));
            }
            else
            {
                this->SendData(S"\0");
            }
        }


        // Fields
        private: Byte __gc* Buffer __gc [] = __gc new Byte __gc*[0x2801];
        private: ClosedEventHandler __gc* ClosedEvent;
        private: ConnectedEventHandler __gc* ConnectedEvent;
        private: ConnectFailEventHandler __gc* ConnectFailEvent;
        private: DataArrivalEventHandler __gc* DataArrivalEvent;
        private: IPAddress __gc* LocalIP = Dns::Resolve(Dns::GetHostName())->AddressList[0];
        private: String __gc* ServerIP;
        private: Socket __gc* Sock;
        private: Boolean __gc* SockConnected = false;
        private: Boolean __gc* SockRunning = false;
        private: Boolean __gc* SocksAuthenticated;
        private: Int32 __gc* SocksVersion;
    };
}

